// This file was taken from alsa-utils aplay.c and stripped down

#define _GNU_SOURCE

#include <stdio.h>
#include <malloc.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <time.h>
#include <locale.h>
#include <assert.h>
#include <sys/poll.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/signal.h>
#include <asm/byteorder.h>
#include <libintl.h>
#include <endian.h>
#include <byteswap.h>
#include "asoundlib.h"

// Definitions for Microsoft WAVE format
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define COMPOSE_ID(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))
#define LE_SHORT(v) (v)
#define LE_INT(v) (v)
#define BE_SHORT(v) bswap_16(v)
#define BE_INT(v) bswap_32(v)
#elif __BYTE_ORDER == __BIG_ENDIAN
#define COMPOSE_ID(a,b,c,d) ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24))
#define LE_SHORT(v) bswap_16(v)
#define LE_INT(v) bswap_32(v)
#define BE_SHORT(v) (v)
#define BE_INT(v) (v)
#else
#error "Wrong endian"
#endif

#define WAV_RIFF COMPOSE_ID('R','I','F','F')
#define WAV_WAVE COMPOSE_ID('W','A','V','E')
#define WAV_FMT COMPOSE_ID('f','m','t',' ')
#define WAV_DATA COMPOSE_ID('d','a','t','a')

// WAVE fmt block constants from Microsoft mmreg.h header
#define WAV_FMT_PCM 0x0001
#define WAV_FMT_IEEE_FLOAT 0x0003

// it's in chunks like .voc and AMIGA iff, but my source say there
// are in only in this combination, so I combined them in one header;(char *)"../../output.wav"
//it works on all WAVE-file I have
typedef struct
{
	u_int magic;		// 'RIFF'
	u_int length;		// filelen
	u_int type;			// 'WAVE'
}
WaveHeader;

typedef struct
{
	u_short format;		// see WAV_FMT_*
	u_short channels;
	u_int sample_fq;	// frequence of sample
	u_int byte_p_sec;
	u_short byte_p_spl;	// samplesize; 1 or 2 bytes
	u_short bit_p_spl;	// 8, 12 or 16 bit
}
WaveFmtBody;

typedef struct
{
	u_int type;			// 'data'
	u_int length;		// samplecount
}
WaveChunkHeader;

#define _(msgid) gettext (msgid)
#define gettext_noop(msgid) msgid
#define N_(msgid) gettext_noop (msgid)

#ifndef LLONG_MAX
#define LLONG_MAX 9223372036854775807LL
#endif

#define DEFAULT_FORMAT SND_PCM_FORMAT_U8
#define DEFAULT_SPEED 8000

#define FORMAT_DEFAULT -1
#define FORMAT_RAW 0
#define FORMAT_VOC 1
#define FORMAT_WAVE 2
#define FORMAT_AU 3

// global data
static snd_pcm_sframes_t (*readi_func)(snd_pcm_t *handle, void *buffer, snd_pcm_uframes_t size);
static snd_pcm_sframes_t (*writei_func)(snd_pcm_t *handle, const void *buffer, snd_pcm_uframes_t size);
static snd_pcm_sframes_t (*readn_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size);
static snd_pcm_sframes_t (*writen_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size);

enum
{
	VUMETER_NONE,
	VUMETER_MONO,
	VUMETER_STEREO
};

static char *command;
static snd_pcm_t *handle;

static struct
{
	snd_pcm_format_t format;
	unsigned int channels;
	unsigned int rate;
}
hwparams, rhwparams;

static int timelimit = 3;
static int quiet_mode = 0;
static int file_type = FORMAT_DEFAULT;
static int open_mode = 0;
static int capture_stop = 0;
static snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
static int interleaved = 1;
static int nonblock = 0;
static u_char *audiobuf = NULL;
static snd_pcm_uframes_t chunk_size = 0;
static unsigned period_time = 0;
static unsigned buffer_time = 0;
static snd_pcm_uframes_t period_frames = 0;
static snd_pcm_uframes_t buffer_frames = 0;
static int avail_min = -1;
static int start_delay = 0;
static int stop_delay = 0;
static int verbose = 0;
static int vumeter = VUMETER_NONE;
static size_t bits_per_sample, bits_per_frame;
static size_t chunk_bytes;
static snd_output_t *log;

static int fd = -1;
static off64_t pbrec_count = LLONG_MAX, fdcount = 0;

// Local prototypes
int run(char *filename);
static void capture(char *filename);
static void begin_wave(int fd, size_t count);
static void end_wave(int fd);

struct fmt_capture
{
	void (*start) (int fd, size_t count);
	void (*end) (int fd);
	char *what;
	long long max_filesize;
}

fmt_rec_table[] =
{
		{	NULL,		NULL,		(char *)N_("raw data"),		LLONG_MAX },
		{	NULL,		NULL,		(char *)N_("VOC"),			16000000LL },
		{	begin_wave,	end_wave,	(char *)N_("WAVE"),			2147483648LL },
		{	NULL,		NULL,		(char *)N_("Sparc Audio"),	LLONG_MAX }
};

#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
#define error(...) do {\
		fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
		fprintf(stderr, __VA_ARGS__); \
		putc('\n', stderr); \
} while (0)
#else
#define error(args...) do {\
		fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
		fprintf(stderr, ##args); \
		putc('\n', stderr); \
} while (0)
#endif

enum
{
	OPT_VERSION = 1,
	OPT_PERIOD_SIZE,
	OPT_BUFFER_SIZE,
	OPT_DISABLE_RESAMPLE,
	OPT_DISABLE_CHANNELS,
	OPT_DISABLE_FORMAT,
	OPT_DISABLE_SOFTVOL,
	OPT_TEST_POSITION
};

int main()
{
	run("../../output.wav");
	return 0;
}

void stop()
{
	capture_stop = 1;
}

int run(char *filename)
{
	capture_stop = 0;
	char *pcm_name = (char *)"default";
	int err;
	snd_pcm_info_t *info;

	snd_pcm_info_alloca(&info);

	err = snd_output_stdio_attach(&log, stderr, 0);
	assert(err >= 0);

	file_type = FORMAT_DEFAULT;
	stream = SND_PCM_STREAM_CAPTURE;
	file_type = FORMAT_WAVE;
	command = (char *)"arecord";
	start_delay = 1;

	chunk_size = -1;
	rhwparams.format = DEFAULT_FORMAT;
	rhwparams.rate = DEFAULT_SPEED;
	rhwparams.channels = 1;

	file_type = FORMAT_WAVE;

	// cdr:
	// rhwparams.format = SND_PCM_FORMAT_S16_BE;
	rhwparams.format = file_type == FORMAT_AU ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_S16_LE;
	rhwparams.rate = 16000; // 44100;
	rhwparams.channels = 1; // 2;

	err = snd_pcm_open(&handle, pcm_name, stream, open_mode);
	if (err < 0)
	{
		error(_("audio open error: %s"), snd_strerror(err));
		return 1;
	}

	if ((err = snd_pcm_info(handle, info)) < 0)
	{
		error(_("info error: %s"), snd_strerror(err));
		return 1;
	}

	if (nonblock)
	{
		err = snd_pcm_nonblock(handle, 1);
		if (err < 0)
		{
			error(_("nonblock setting error: %s"), snd_strerror(err));
			return 1;
		}
	}

	chunk_size = 1024;
	hwparams = rhwparams;

	audiobuf = (u_char *)malloc(1024);
	if (audiobuf == NULL)
	{
		error(_("not enough memory"));
		return 1;
	}

	writei_func = snd_pcm_writei;
	readi_func  = snd_pcm_readi;
	writen_func = snd_pcm_writen;
	readn_func  = snd_pcm_readn;

	// Capture audio to file
	capture(filename);

	if (fmt_rec_table[file_type].end)
	{
		fmt_rec_table[file_type].end(fd);
		fd = -1;
	}
	stream = SND_PCM_STREAM_CAPTURE;
	if (fd > 1)
	{
		close(fd);
		fd = -1;
	}

	if (handle)
	{
		snd_pcm_close(handle);
		handle = NULL;
	}

	return EXIT_SUCCESS;
}

static void set_params(void)
{
	snd_pcm_hw_params_t *params;
	snd_pcm_sw_params_t *swparams;
	snd_pcm_uframes_t buffer_size;
	int err;
	size_t n;
	unsigned int rate;
	snd_pcm_uframes_t start_threshold, stop_threshold;

	snd_pcm_hw_params_alloca(&params);
	snd_pcm_sw_params_alloca(&swparams);

	err = snd_pcm_hw_params_any(handle, params);
	if (err < 0)
	{
		error(_("Broken configuration for this PCM: no configurations available"));
		exit(EXIT_FAILURE);
	}
	else if (interleaved)
		err = snd_pcm_hw_params_set_access(handle, params,
				SND_PCM_ACCESS_RW_INTERLEAVED);
	else
		err = snd_pcm_hw_params_set_access(handle, params,
				SND_PCM_ACCESS_RW_NONINTERLEAVED);
	if (err < 0)
	{
		error(_("Access type not available"));
		exit(EXIT_FAILURE);
	}

	err = snd_pcm_hw_params_set_format(handle, params, hwparams.format);
	if (err < 0)
	{
		error(_("Sample format non available"));
		exit(EXIT_FAILURE);
	}

	err = snd_pcm_hw_params_set_channels(handle, params, hwparams.channels);
	if (err < 0)
	{
		error(_("Channels count non available"));
		exit(EXIT_FAILURE);
	}

#if 0
	err -1= snd_pcm_hw_params_set_periods_min(handle, params, 2);
	assert(err >= 0);
#endif

	rate = hwparams.rate;
	err = snd_pcm_hw_params_set_rate_near(handle, params, &hwparams.rate, 0);
	assert(err >= 0);

	if ((float)rate * 1.05 < hwparams.rate || (float)rate * 0.95 > hwparams.rate)
	{
		if (!quiet_mode)
		{
			char plugex[64];
			const char *pcmname = snd_pcm_name(handle);
			fprintf(stderr, _("Warning: rate is not accurate (requested = %iHz, got = %iHz)\n"), rate, hwparams.rate);
			if (! pcmname || strchr(snd_pcm_name(handle), ':'))
				*plugex = 0;
			else
				snprintf(plugex, sizeof(plugex), "(-Dplug:%s)",
						snd_pcm_name(handle));
			fprintf(stderr, _(" please, try the plug plugin %s\n"),
					plugex);
		}
	}

	rate = hwparams.rate;
	if (buffer_time == 0 && buffer_frames == 0)
	{
		err = snd_pcm_hw_params_get_buffer_time_max(params,
				&buffer_time, 0);
		assert(err >= 0);
		if (buffer_time > 500000)
			buffer_time = 500000;
	}

	if (period_time == 0 && period_frames == 0)
	{
		if (buffer_time > 0)
			period_time = buffer_time / 4;
		else
			period_frames = buffer_frames / 4;
	}

	if (period_time > 0)
		err = snd_pcm_hw_params_set_period_time_near(handle, params,
				&period_time, 0);
	else
		err = snd_pcm_hw_params_set_period_size_near(handle, params,
				&period_frames, 0);
	assert(err >= 0);

	if (buffer_time > 0)
	{
		err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
				&buffer_time, 0);
	}
	else
	{
		err = snd_pcm_hw_params_set_buffer_size_near(handle, params,
				&buffer_frames);
	}
	assert(err >= 0);

	err = snd_pcm_hw_params(handle, params);
	if (err < 0)
	{
		error(_("Unable to install hw params:"));
		snd_pcm_hw_params_dump(params, log);
		exit(EXIT_FAILURE);
	}

	snd_pcm_hw_params_get_period_size(params, &chunk_size, 0);
	snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
	if (chunk_size == buffer_size)
	{
		error(_("Can't use period equal to buffer size (%lu == %lu)"),
				chunk_size, buffer_size);
		exit(EXIT_FAILURE);
	}

	snd_pcm_sw_params_current(handle, swparams);
	if (avail_min < 0)
		n = chunk_size;
	else
		n = (double) rate * avail_min / 1000000;
	err = snd_pcm_sw_params_set_avail_min(handle, swparams, n);

	// Round up to closest transfer boundary
	n = buffer_size;
	if (start_delay <= 0)
	{
		start_threshold = n + (double) rate * start_delay / 1000000;
	}
	else
		start_threshold = (double) rate * start_delay / 1000000;

	if (start_threshold < 1)
		start_threshold = 1;
	if (start_threshold > n)
		start_threshold = n;

	err = snd_pcm_sw_params_set_start_threshold(handle, swparams, start_threshold);
	assert(err >= 0);
	if (stop_delay <= 0)
		stop_threshold = buffer_size + (double) rate * stop_delay / 1000000;
	else
		stop_threshold = (double) rate * stop_delay / 1000000;

	err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, stop_threshold);
	assert(err >= 0);

	if (snd_pcm_sw_params(handle, swparams) < 0)
	{
		error(_("unable to install sw params:"));
		snd_pcm_sw_params_dump(swparams, log);
		exit(EXIT_FAILURE);
	}

	if (verbose)
		snd_pcm_dump(handle, log);

	bits_per_sample = snd_pcm_format_physical_width(hwparams.format);
	bits_per_frame = bits_per_sample * hwparams.channels;
	chunk_bytes = chunk_size * bits_per_frame / 8;
	audiobuf = (u_char *)realloc(audiobuf, chunk_bytes);
	if (audiobuf == NULL)
	{
		error(_("not enough memory"));
		exit(EXIT_FAILURE);
	}

// 	fprintf(stderr, "real chunk_size = %i, frags = %i, total = %i\n", chunk_size, setup.buf.block.frags, setup.buf.block.frags * chunk_size);

	// stereo VU-meter isn't always available...
	if (vumeter == VUMETER_STEREO)
	{
		if (hwparams.channels != 2 || !interleaved || verbose > 2)
			vumeter = VUMETER_MONO;
	}

	buffer_frames = buffer_size;	// For position test
}

#ifndef timersub
#define timersub(a, b, result) \
		do { \
			(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
			(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
			if ((result)->tv_usec < 0) { \
				--(result)->tv_sec; \
				(result)->tv_usec += 1000000; \
			} \
		} while (0)
#endif

// I/O error handler
static void xrun(void)
{
	snd_pcm_status_t *status;
	int res;

	snd_pcm_status_alloca(&status);
	if ((res = snd_pcm_status(handle, status)) < 0)
	{
		error(_("status error: %s"), snd_strerror(res));
		exit(EXIT_FAILURE);
	}

	if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN)
	{
		struct timeval now, diff, tstamp;
		gettimeofday(&now, 0);
		snd_pcm_status_get_trigger_tstamp(status, &tstamp);
		timersub(&now, &tstamp, &diff);
		fprintf(stderr, _("%s!!! (at least %.3f ms long)\n"),
				stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"),
						diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
		if (verbose)
		{
			fprintf(stderr, _("Status:\n"));
			snd_pcm_status_dump(status, log);
		}

		if ((res = snd_pcm_prepare(handle)) < 0)
		{
			error(_("xrun: prepare error: %s"), snd_strerror(res));
			exit(EXIT_FAILURE);
		}

		return;	// ok, data should be accepted again
	}

	if (snd_pcm_status_get_state(status) == SND_PCM_STATE_DRAINING)
	{
		if (verbose)
		{
			fprintf(stderr, _("Status(DRAINING):\n"));
			snd_pcm_status_dump(status, log);
		}

		if (stream == SND_PCM_STREAM_CAPTURE)
		{
			fprintf(stderr, _("capture stream format change? attempting recover...\n"));
			if ((res = snd_pcm_prepare(handle))<0)
			{
				error(_("xrun(DRAINING): prepare error: %s"), snd_strerror(res));
				exit(EXIT_FAILURE);
			}
			return;
		}
	}

	if (verbose)
	{
		fprintf(stderr, _("Status(R/W):\n"));
		snd_pcm_status_dump(status, log);
	}

	error(_("read/write error, state = %s"), snd_pcm_state_name(snd_pcm_status_get_state(status)));
	exit(EXIT_FAILURE);
}

// I/O suspend handler
static void suspend(void)
{
	int res;

	if (!quiet_mode)
		fprintf(stderr, _("Suspended. Trying resume. ")); fflush(stderr);

	while ((res = snd_pcm_resume(handle)) == -EAGAIN)
		sleep(1);	// Wait until suspend flag is released

	if (res < 0)
	{
		if (!quiet_mode)
			fprintf(stderr, _("Failed. Restarting stream. ")); fflush(stderr);
		if ((res = snd_pcm_prepare(handle)) < 0)
		{
			error(_("suspend: prepare error: %s"), snd_strerror(res));
			exit(EXIT_FAILURE);
		}
	}

	if (!quiet_mode)
		fprintf(stderr, _("Done.\n"));
}

static void print_vu_meter_mono(int perc, int maxperc)
{
	const int bar_length = 50;
	char line[80];
	int val;

	for (val = 0; val <= perc * bar_length / 100 && val < bar_length; val++)
		line[val] = '#';
	for (; val <= maxperc * bar_length / 100 && val < bar_length; val++)
		line[val] = ' ';
	line[val] = '+';
	for (++val; val <= bar_length; val++)
		line[val] = ' ';
	if (maxperc > 99)
		sprintf(line + val, "| MAX");
	else
		sprintf(line + val, "| %02i%%", maxperc);
	fputs(line, stdout);
	if (perc > 100)
		printf(_(" !clip "));
}

static void print_vu_meter_stereo(int *perc, int *maxperc)
{
	const int bar_length = 35;
	char line[80];
	int c;

	memset(line, ' ', sizeof(line) - 1);
	line[bar_length + 3] = '|';

	for (c = 0; c < 2; c++)
	{
		int p = perc[c] * bar_length / 100;
		char tmp[4];

		if (p > bar_length)
			p = bar_length;

		if (c)
			memset(line + bar_length + 6 + 1, '#', p);
		else
			memset(line + bar_length - p - 1, '#', p);

		p = maxperc[c] * bar_length / 100;
		if (p > bar_length)
			p = bar_length;

		if (c)
			line[bar_length + 6 + 1 + p] = '+';
		else
			line[bar_length - p - 1] = '+';

		if (maxperc[c] > 99)
			sprintf(tmp, "MAX");
		else
			sprintf(tmp, "%02d%%", maxperc[c]);

		if (c)
			memcpy(line + bar_length + 3 + 1, tmp, 3);
		else
			memcpy(line + bar_length, tmp, 3);
	}
	line[bar_length * 2 + 6 + 2] = 0;
	fputs(line, stdout);
}

static void print_vu_meter(signed int *perc, signed int *maxperc)
{
	if (vumeter == VUMETER_STEREO)
		print_vu_meter_stereo(perc, maxperc);
	else
		print_vu_meter_mono(*perc, *maxperc);
}

// Peak handler
static void compute_max_peak(u_char *data, size_t count)
{
	signed int val, max, perc[2], max_peak[2];
	static	int	run = 0;
	size_t ocount = count;
	int	format_little_endian = snd_pcm_format_little_endian(hwparams.format);
	int ichans, c;

	if (vumeter == VUMETER_STEREO)
		ichans = 2;
	else
		ichans = 1;

	memset(max_peak, 0, sizeof(max_peak));
	switch (bits_per_sample)
	{
	case 8:
	{
		signed char *valp = (signed char *)data;
		signed char mask = snd_pcm_format_silence(hwparams.format);
		c = 0;
		while (count-- > 0)
		{
			val = *valp++ ^ mask;
			val = abs(val);
			if (max_peak[c] < val)
				max_peak[c] = val;
			if (vumeter == VUMETER_STEREO)
				c = !c;
		}
		break;
	}

	case 16:
	{
		signed short *valp = (signed short *)data;
		signed short mask = snd_pcm_format_silence_16(hwparams.format);
		signed short sval;

		count /= 2;
		c = 0;
		while (count-- > 0)
		{
			if (format_little_endian)
				sval = __le16_to_cpu(*valp);
			else
				sval = __be16_to_cpu(*valp);
			sval = abs(sval) ^ mask;
			if (max_peak[c] < sval)
				max_peak[c] = sval;
			valp++;
			if (vumeter == VUMETER_STEREO)
				c = !c;
		}
		break;
	}

	case 24:
	{
		unsigned char *valp = data;
		signed int mask = snd_pcm_format_silence_32(hwparams.format);

		count /= 3;
		c = 0;
		while (count-- > 0)
		{
			if (format_little_endian)
			{
				val = valp[0] | (valp[1] << 8) | (valp[2] << 16);
			}
			else
			{
				val = (valp[0] << 16) | (valp[1] << 8) | valp[2];
			}

			// Correct signed bit in 32-bit value
			if (val & (1 << (bits_per_sample-1)))
			{
				val |= 0xff << 24; // Negate upper bits too
			}
			val = abs(val) ^ mask;
			if (max_peak[c] < val)
				max_peak[c] = val;
			valp += 3;
			if (vumeter == VUMETER_STEREO)
				c = !c;
		}
		break;
	}

	case 32:
	{
		signed int *valp = (signed int *)data;
		signed int mask = snd_pcm_format_silence_32(hwparams.format);

		count /= 4;
		c = 0;
		while (count-- > 0)
		{
			if (format_little_endian)
				val = __le32_to_cpu(*valp);
			else
				val = __be32_to_cpu(*valp);
			val = abs(val) ^ mask;
			if (max_peak[c] < val)
				max_peak[c] = val;
			valp++;
			if (vumeter == VUMETER_STEREO)
				c = !c;
		}
		break;
	}

	default:
		if (run == 0)
		{
			fprintf(stderr, _("Unsupported bit size %d.\n"), (int)bits_per_sample);
			run = 1;
		}
		return;
	}

	max = 1 << (bits_per_sample-1);
	if (max <= 0)
		max = 0x7fffffff;

	for (c = 0; c < ichans; c++)
	{
		if (bits_per_sample > 16)
			perc[c] = max_peak[c] / (max / 100);
		else
			perc[c] = max_peak[c] * 100 / max;
	}

	if (interleaved && verbose <= 2)
	{
		static int maxperc[2];
		static time_t t=0;
		const time_t tt=time(NULL);
		if (tt > t)
		{
			t = tt;
			maxperc[0] = 0;
			maxperc[1] = 0;
		}

		for (c = 0; c < ichans; c++)
			if (perc[c] > maxperc[c])
				maxperc[c] = perc[c];

		putchar('\r');
		print_vu_meter(perc, maxperc);
		fflush(stdout);
	}
	else if (verbose == 3)
	{
		printf(_("Max peak (%li samples): 0x%08x "), (long)ocount, max_peak[0]);
		for (val = 0; val < 20; val++)
			if (val <= perc[0] / 5)
				putchar('#');
			else
				putchar(' ');
		printf(" %i%%\n", perc[0]);
		fflush(stdout);
	}
}

// Read function
static ssize_t pcm_read(u_char *data, size_t rcount)
{
	ssize_t r;
	size_t result = 0;
	size_t count = rcount;

	if (count != chunk_size)
	{
		count = chunk_size;
	}

	while (count > 0)
	{
		r = readi_func(handle, data, count);
		if (r == -EAGAIN || (r >= 0 && (size_t)r < count))
		{
			snd_pcm_wait(handle, 1000);
		}
		else if (r == -EPIPE)
		{
			xrun();
		}
		else if (r == -ESTRPIPE)
		{
			suspend();
		}
		else if (r < 0)
		{
			error(_("read error: %s"), snd_strerror(r));
			exit(EXIT_FAILURE);
		}

		if (r > 0)
		{
			if (vumeter)
				compute_max_peak(data, r * hwparams.channels);
			result += r;
			count -= r;
			data += r * bits_per_frame / 8;
		}
	}

	return rcount;
}

// calculate the data count to read from/to dsp
static off64_t calc_count(void)
{
	off64_t count;

	if (timelimit == 0)
	{
		count = pbrec_count;
	}
	else
	{
		count = snd_pcm_format_size(hwparams.format, hwparams.rate * hwparams.channels);
		count *= (off64_t)timelimit;
	}

	return count < pbrec_count ? count : pbrec_count;
}

// Write a WAVE-header
static void begin_wave(int fd, size_t cnt)
{
	WaveHeader h;
	WaveFmtBody f;
	WaveChunkHeader cf, cd;
	int bits;
	u_int tmp;
	u_short tmp2;

	// WAVE cannot handle greater than 32bit (signed?) int
	if (cnt == (size_t)-2)
		cnt = 0x7fffff00;

	bits = 8;
	switch ((unsigned long) hwparams.format)
	{
	case SND_PCM_FORMAT_U8:
		bits = 8;
		break;

	case SND_PCM_FORMAT_S16_LE:
		bits = 16;
		break;

	case SND_PCM_FORMAT_S32_LE:
	case SND_PCM_FORMAT_FLOAT_LE:
		bits = 32;
		break;

	case SND_PCM_FORMAT_S24_LE:
	case SND_PCM_FORMAT_S24_3LE:
		bits = 24;
		break;

	default:
		error(_("Wave doesn't support %s format..."), snd_pcm_format_name(hwparams.format));
		exit(EXIT_FAILURE);
	}

	h.magic = WAV_RIFF;
	tmp = cnt + sizeof(WaveHeader) + sizeof(WaveChunkHeader) + sizeof(WaveFmtBody) + sizeof(WaveChunkHeader) - 8;
	h.length = LE_INT(tmp);
	h.type = WAV_WAVE;

	cf.type = WAV_FMT;
	cf.length = LE_INT(16);

	if (hwparams.format == SND_PCM_FORMAT_FLOAT_LE)
		f.format = LE_SHORT(WAV_FMT_IEEE_FLOAT);
	else
		f.format = LE_SHORT(WAV_FMT_PCM);
	f.channels = LE_SHORT(hwparams.channels);
	f.sample_fq = LE_INT(hwparams.rate);

#if 0
	tmp2 = (samplesize == 8) ? 1 : 2;
	f.byte_p_spl = LE_SHORT(tmp2);
	tmp = dsp_speed * hwparams.channels * (u_int) tmp2;
#else
	tmp2 = hwparams.channels * snd_pcm_format_physical_width(hwparams.format) / 8;
	f.byte_p_spl = LE_SHORT(tmp2);
	tmp = (u_int) tmp2 * hwparams.rate;
#endif
	f.byte_p_sec = LE_INT(tmp);
	f.bit_p_spl = LE_SHORT(bits);

	cd.type = WAV_DATA;
	cd.length = LE_INT(cnt);

	if (write(fd, &h,  sizeof(WaveHeader)) 		!= sizeof(WaveHeader) ||
		write(fd, &cf, sizeof(WaveChunkHeader)) != sizeof(WaveChunkHeader) ||
		write(fd, &f,  sizeof(WaveFmtBody)) 	!= sizeof(WaveFmtBody) ||
		write(fd, &cd, sizeof(WaveChunkHeader)) != sizeof(WaveChunkHeader))
	{
		error(_("write error"));
		exit(EXIT_FAILURE);
	}
}

// Only close output
static void end_wave(int fd)
{
	WaveChunkHeader cd;
	off64_t length_seek;
	off64_t filelen;
	u_int rifflen;

	length_seek = sizeof(WaveHeader) +
				  sizeof(WaveChunkHeader) +
				  sizeof(WaveFmtBody);
	cd.type = WAV_DATA;
	cd.length = fdcount > 0x7fffffff ? LE_INT(0x7fffffff) : LE_INT(fdcount);
	filelen = fdcount + 2*sizeof(WaveChunkHeader) + sizeof(WaveFmtBody) + 4;
	rifflen = filelen > 0x7fffffff ? LE_INT(0x7fffffff) : LE_INT(filelen);

	if (lseek64(fd, 4, SEEK_SET) == 4)
		write(fd, &rifflen, 4);

	if (lseek64(fd, length_seek, SEEK_SET) == length_seek)
		write(fd, &cd, sizeof(WaveChunkHeader));

	if (fd != 1)
		close(fd);
}

static int new_capture_file(char *name, char *namebuf,
							size_t namelen, int filecount)
{
	// Get a copy of the original filename
	char *s;
	char buf[PATH_MAX+1];

	strncpy(buf, name, sizeof(buf));

	// Separate extension from filename
	s = buf + strlen(buf);
	while (s > buf && *s != '.' && *s != '/')
		--s;
	if (*s == '.')
		*s++ = 0;
	else if (*s == '/')
		s = buf + strlen(buf);

	// Upon first jump to this if block rename the first file
	if (filecount == 1)
	{
		if (*s)
			snprintf(namebuf, namelen, "%s-01.%s", buf, s);
		else
			snprintf(namebuf, namelen, "%s-01", buf);
		remove(namebuf);
		rename(name, namebuf);
		filecount = 2;
	}

	// Name of the current file
	if (*s)
		snprintf(namebuf, namelen, "%s-%02i.%s", buf, filecount, s);
	else
		snprintf(namebuf, namelen, "%s-%02i", buf, filecount);

	return filecount;
}

static void capture(char *orig_name)
{
	int tostdout = 0;			// Boolean which describes output stream
	int filecount = 0;			// Number of files written
	char *name = orig_name;		// Current filename
	char namebuf[PATH_MAX+1];	// Full file name
	off64_t count, rest;		// Number of bytes to capture

	// get number of bytes to capture
	count = calc_count();
	if (count == 0)
		count = LLONG_MAX;

	// WAVE-file should be even (I'm not sure), but wasting one byte
	// isn't a problem (this can only be in 8 bit mono)
	if (count < LLONG_MAX)
		count += count % 2;
	else
		count -= count % 2;

	// setup sound hardware
	set_params();

	printf("arecord: Recording audio to: %s on %d channels at %d Hz\n", name, hwparams.channels, hwparams.rate);

	// Write to stdout
	if (!name || !strcmp(name, "-"))
	{
		fd = fileno(stdout);
		name = (char *)"stdout";
		tostdout=1;
		if (count > fmt_rec_table[file_type].max_filesize)
			count = fmt_rec_table[file_type].max_filesize;
	}

	do
	{
		// open a file to write
		if (!tostdout)
		{
			// upon the second file we start the numbering scheme */
			if (filecount)
			{
				filecount = new_capture_file(orig_name, namebuf,
											 sizeof(namebuf), filecount);
				name = namebuf;
			}

			// Open a new file
			remove(name);
			if ((fd = open64(name, O_WRONLY | O_CREAT, 0644)) == -1)
			{
				perror(name);
				exit(EXIT_FAILURE);
			}
			filecount++;
		}

		rest = count;
		if (rest > fmt_rec_table[file_type].max_filesize)
			rest = fmt_rec_table[file_type].max_filesize;

		// Setup sample header
		if (fmt_rec_table[file_type].start)
			fmt_rec_table[file_type].start(fd, rest);

		// Capture
		fdcount = 0;
		while (rest > 0 && capture_stop == 0)
		{
			size_t c = (rest <= (off64_t)chunk_bytes) ? (size_t)rest : chunk_bytes;
			size_t f = c * 8 / bits_per_frame;

			if ((pcm_read(audiobuf, f)) != (ssize_t)f)
				break;

			if ((write(fd, audiobuf, c)) != (ssize_t)c)
			{
				perror(name);
				exit(EXIT_FAILURE);
			}

			count -= c;
			rest -= c;
			fdcount += c;
		}

		// Finish sample container
		if (fmt_rec_table[file_type].end && !tostdout)
		{
			printf("arecord: Finished sample container\n");

			fmt_rec_table[file_type].end(fd);
			fd = -1;
		}

		// Repeat the loop when format is raw without timelimit or
		// requested counts of data are recorded
	}
	while (((file_type == FORMAT_RAW && !timelimit) || count > 0) && capture_stop == 0);

	printf("arecord: Stopping capturing audio. count=%llu capture_stop=%d\n", count, capture_stop);
}

